//=============================================================================
// System  : MatchKit
// File    : NumberPattern.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using System.Globalization;
using System.Runtime.Serialization;

namespace MatchKit.Patterns
{
	/// <summary>
	/// Provides the logic to match a number as a sequence of sign, digits and decimals.
	/// </summary>
	/// <threadsafety static="true" instance="false" />
	/// <serialization binary="true" contract="true" />
	/// <remarks>
	/// Whitespaces are permitted between the sign and the first digit.
	/// </remarks>
	/// <conceptualLink target="cb785773-b79b-4cc4-acc4-6688cdb226b6" />
	/// <conceptualLink target="0a06b2a9-5f58-409a-ba62-639cfd77bca7" />
	#if DC
	[DataContract]
	#endif
	#if SZ
	[Serializable]
	#endif
	public sealed class NumberPattern : BasePattern
	{
		private bool _isSignSupported = true;
		/// <summary>
		/// Gets or sets the option to support the plus or minus sign before the number. 
		/// Default is <b>true</b>.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public bool IsSignSupported
		{
			get { return _isSignSupported; }
			set { _isSignSupported = value; }
		}

		private bool _isDecimalSupported = true;
		/// <summary>
		/// Gets or sets the option to support decimal numbers. 
		/// Default is <b>true</b>.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public bool IsDecimalSupported
		{
			get { return _isDecimalSupported; }
			set { _isDecimalSupported = value; }
		}

		private byte _numberOfDigits = 0;
		/// <summary>
		/// Gets or sets the exact number of digits expected. A value of zero 
		/// specify any number of digits. Default is <b>0</b>.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public byte NumberOfDigits
		{
			get { return _numberOfDigits; }
			set { _numberOfDigits = value; }
		}

		/// <inheritdoc />
		protected override Match OnMatch(MatchContext context)
		{
			bool isMinus = false;

			if (context.IsMinus || context.IsPlus)
			{
				if (!_isSignSupported)
					return Error(context, "Sign is not supported for this pattern instance");

				isMinus = context.IsMinus;
				context.NextCharacter();	// skip + or -
				// skip spaces
				context.NextTo(ch => char.IsWhiteSpace(ch));
			}

			if (!context.IsDigit)
				return Error(context);

			bool isDecimal = false;
			int digitsNo = 0;

			var capture = new Capture(context, true);
			context.NextTo(ch =>
				{
					if (!char.IsDigit(ch))
						return false;
					digitsNo++;
					return true;
				});

			if (context.Current == '.' && char.IsDigit(context.Peek()))
			{
				if (!_isDecimalSupported)
					return Error(context, "Decimal number is not supported for this pattern instance");

				isDecimal = true;
				context.NextCharacter();	// skip '.'

				context.NextTo(ch =>
				{
					if (!char.IsDigit(ch))
						return false;
					digitsNo++;
					return true;
				});
			}

			if (_numberOfDigits > 0)
			{
				if (digitsNo > _numberOfDigits)
					return Error(context, "Number of digits exceed the specified number of " + _numberOfDigits);
				else if (digitsNo < _numberOfDigits)
					return Error(context, "Number of digits not reach the specified number of " + _numberOfDigits);
			}

			var captured = (isMinus ? "-" : "") + capture.ToString();

			object number = null;

			try
			{
				if (isDecimal)
					number = decimal.Parse(captured, CultureInfo.InvariantCulture);
				else
					number = long.Parse(captured, CultureInfo.InvariantCulture);
			}
			catch
			{
				throw new InvalidCastException("String [" + captured + "] cannot be converted to a valid .NET number");
			}

			return Success(context, number);
		}

		/// <inheritdoc />
		public override string HelpString
		{
			get { return "Number"; }
		}
	}
}
